package com.bit.java_doc_searcher.searcher;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;

public class Parser {

    // 先指定一个加载文档的路径
    private static final String INPUT_PATH = "D:/Java/javaDocument/docs/api";
    // 创建一个 searcher.Index 实例
    private Index index = new Index();

    private AtomicLong t1 = new AtomicLong(0);
    private AtomicLong t2 = new AtomicLong(0);

    // 通过这个方法实现单线程实现索引
    public void run(){
        long beg = System.currentTimeMillis();
        System.out.println("索引制作开始！");
        // 整个 searcher.Parser 类的入口
        // 1.根据上面指定的路径，枚举出该路径中所有的文件（html）,
        // 这个过程需要把所有子目录中的文件都能获取到 用ArrayList来接收递归的文件
        ArrayList<File> fileList = new ArrayList<File>();

        enumFile(INPUT_PATH,fileList);
        // System.out.println(fileList);
        // System.out.println(fileList.size());
        long endenumFile = System.currentTimeMillis();
        System.out.println("枚举文件完毕！ 消耗时间："+(endenumFile-beg)+"ms");
        // 2.针对上面罗列出的文件的路径，打开文件，读取文件内容，并进行解析，并构建索引
        for (File f: fileList) {
            System.out.println("开始解析："+f.getAbsolutePath());
            // 通过这个方法来解析单个的 HTML 文件
            parseHTML(f);
        }
        long endFor = System.currentTimeMillis();
        System.out.println("遍历文件完毕！ 消耗时间："+(endFor-endenumFile)+"ms");
        // 3. 把在内存中构造好的索引数据结构，保存到指定的文件中
        index.save();
        long end = System.currentTimeMillis();
        System.out.println("索引制作完毕！ 消耗时间：" + (end-beg) + "ms");
    }
    // 通过这个方法实现 “多线程制作索引”
    public void runByThread(){
        long beg = System.currentTimeMillis();
        System.out.println("索引制作开始！");

        // 1.枚举出所有的文件
        ArrayList<File> files = new ArrayList<>();
        enumFile(INPUT_PATH,files);
        // 2.遍历循环文件，此处为了能够通过多线程制作索引，就直接引入线程池
        final CountDownLatch latch = new CountDownLatch(files.size());
        ExecutorService executorService = Executors.newFixedThreadPool(8);
        for (final File f: files) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("解析"+ f.getAbsolutePath());
                    parseHTML(f);
                    latch.countDown();
                }
            });
        }
        try {
            latch.await(); // 如果 countDown的个数达不到 size()的个数，就会死等
            // 手动的把线程池里面的线程都干掉
            executorService.shutdown();

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 3.保存索引
        index.save();

        long end = System.currentTimeMillis();
        System.out.println("索引制作完毕！ 消耗时间："+ (end-beg) + "ms");
        System.out.println("t1: "+t1+"t2: "+t2);
    }

    private void parseHTML(File f) {
        // 1.解析出HTML的标题
        String title = parseTitle(f);
        // 2.解析出 HTML 对应的 URL
        String url = parseUrl(f);
        // 3.解析出 HTML 对应的正文（有了正文才有后续的描述）
        long beg = System.nanoTime();
        String content = parseContentByRegex(f);
        long mid = System.nanoTime();
        // 4. 把解析出来的这些信息，加入到索引当中
        index.addDoc(title,url,content);
        long end = System.nanoTime();
        // 注意：由于 parseHTML 会被循环调用多次，单词调用其实时间较短
        // 加入频繁的打印反而可能会拖慢这个速度本身
        t1.addAndGet(mid-beg);
        t2.addAndGet(end-mid);
    }
    private String parseTitle(File f) {
        return f.getName().substring(0,f.getName().length()-".html".length());
    }

    private String parseUrl(File f) {
        String part1 = "https://docs.oracle.com/javase/8/docs/api";
        String part2 = f.getAbsolutePath().substring(INPUT_PATH.length());
        return part1+part2;
    }
    private String readFile(File f){
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(f))) {
           StringBuilder content = new StringBuilder();
           while (true){
               int ret = bufferedReader.read();
               if(ret==-1){
                   break;
               }
               char c = (char)ret;
               if(c=='\n' || c=='\r'){
                   c = ' ';
               }
               content.append(c);
           }
           return content.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    // 这个方法内部就基于正则表达式，实现去标签，以及去除 script
    public String parseContentByRegex(File f){
        // 1.先把整个文件都读到 String 里面
        String content = readFile(f);
        // 2.替换掉 script 标签
        content = content.replaceAll("<script.*?>(.*?)</script>"," ");
        // 3.替换掉普通的 html 标签
        content = content.replaceAll("<.*?>"," ");
        // 4.使用正则把多个空格，合并成一个空格
        content = content.replaceAll("\\s+"," ");
        return content;
    }

    public String parseContent(File f){
        // 先按照一个字符一个字符的方式来读取，以 < 和 > 来控制拷贝数据的开关
        // 手动把缓冲区设置成 1M 大小
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(f),1024*1024)) {

            // 加上一个是否要进行拷贝，开关
            boolean isCopy = true;
            // 还得准备一个保存结果的 StringBuilder
            StringBuilder content = new StringBuilder();
            while (true){
                // 创建FIleReader对象成功后 使用read() 读取单个字符，返回一个int型变量代表读取到的字符
                // 此处使用 int 作为返回值，主要是为了表示一些非法情况
                // 如果读到文件末尾，继续读，就会返回 -1
                int ret = bufferedReader.read();
                if(ret == -1){
                    // 表示文件读完了
                    break;
                }
                // 代码走到这，就是一个合法的字符了
                char c = (char)ret;
                // 先判断能不能拷贝 再判断字符
                if(isCopy){
                    // 拷贝状态下 什么时候跳转到不能拷贝
                    if(c == '<'){
                        isCopy = false;
                        // 跳出本次循环 不拷贝 <
                        continue;
                    }
                    // 为了去掉换行 替换成空格
                    if(c == '\n' || c== '\r'){
                        c = ' ';
                    }
                    // 可以拷贝
                    content.append(c);
                }else {
                    // 什么时候结束不能拷贝的状态
                    if(c == '>'){
                        isCopy = true;
                    }
                }
            }
            // 将对象转换成字符串
            return content.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }

    // 第一个参数表示从哪个目录开始进行递归便利
    // 第二个参数表示递归得到得结果
    private void enumFile(String inputPath, ArrayList<File> fileList) {
        // 将传入得文件路径得字符串 转化成 File 对象
        File rootPath = new File(inputPath);
        // 通过 listFiles 方法罗列出当前路径包含了哪些子文件 只能看到一级目录
        File[] lists = rootPath.listFiles();
        for (File f:lists) {
            // 根据当前 f 的类型，来决定是否要递归
            // 如果 f 是一个普通文件，就把 f 加入到 fileList 结果中
            // 如果 f 是一个目录，就递归的调用 enumFile 这个方法，来进一步的获取子目录中的内容
            if(f.isDirectory()){
                // getAbsolutePath() 将 File 对象再转换成字符串的路径
                enumFile(f.getAbsolutePath(),fileList);
            }else {
                // String 类的 endsWith方法 判断以 .html 结尾。
                if(f.getAbsolutePath().endsWith(".html"))
                fileList.add(f);
            }
        }
    }

    public static void main(String[] args) {
        // 通过 main 方法来实现整个制作索引的过程
        Parser parser = new Parser();
        parser.runByThread();
    }
}
